/*
    This is a set of libraries to assist in OpenGl and Other Applications
    Copyright (C) 2008  The Modular Engine Team

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#define OPENGLMODULE
#include "OpenglModule.h"
#include "WindowModule.h"
#include "ImageLoader.h"
#include <math.h>

#define ZCOORD -5

namespace ModularEngine
{
	int OpenglModule::initialize()
	{

		UINT bits = 32; // TODO: Move this into actual class

		PIXELFORMATDESCRIPTOR pfd = 
		{
			sizeof( PIXELFORMATDESCRIPTOR ),
			1, 
			PFD_DRAW_TO_WINDOW |
			PFD_SUPPORT_OPENGL |
			PFD_DOUBLEBUFFER,
			PFD_TYPE_RGBA,
			bits,
			0, 0, 0, 0, 0, 0,
			0,
			0,
			0,
			0, 0, 0, 0,
			16,
			1,  // We have a stencil buffer
			0,
			PFD_MAIN_PLANE,
			0,
			0, 0, 0
		};

		if( !(mhDC=GetDC(ModularEngine::WindowModule::getSingleton().getHwnd())) )
		{
			THROWERROR("Can't Create A GL Device Context.");
			return 0;
		}		

		int PixelFormat = 0;

		if( !(PixelFormat=ChoosePixelFormat(mhDC, &pfd)))
		{
			THROWERROR("Can't find a suitable PixelFormat");
			return 0;
		}

		if( !SetPixelFormat( mhDC, PixelFormat, &pfd ))
		{
			THROWERROR("Can't set PixelFormat");
			return 0;
		}

		if( !(mhRC=wglCreateContext(mhDC)))
		{
			THROWERROR("Can't Create an OpenGL Rendering Context.");
			return 0;
		}

		if( !wglMakeCurrent( mhDC, mhRC ) )
		{
			THROWERROR("Can't Activate the OpenGL Rending Context" );
			return 0;
		}


		resizeWindow( 1024, 768 );

		// Setup the font
		mLogFont.lfHeight = -15;
		mLogFont.lfWidth = 0;
		mLogFont.lfEscapement = 0;
		mLogFont.lfOrientation = 0;
		mLogFont.lfWeight = 500;
		mLogFont.lfItalic = 0; // false;
		mLogFont.lfStrikeOut = 0; // false;
		mLogFont.lfCharSet = ANSI_CHARSET;
		mLogFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
		mLogFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
		mLogFont.lfQuality = DEFAULT_QUALITY;
		mLogFont.lfPitchAndFamily = DEFAULT_PITCH;

		// Probably would be best not to use OratorStd as I'm
		// not sure how many systems have it.  
		strcpy( mLogFont.lfFaceName, "OratorStd" );
		mhFont = CreateFontIndirect( &mLogFont );
		SelectObject( mhDC, mhFont );
		mFontList = glGenLists(128);
		wglUseFontBitmaps( mhDC, 0, 128, mFontList );

		DeleteObject( mhFont );
		return 1;
	}

	int OpenglModule::run()
	{
		unsigned char state [255];
		for( int i = 0; i < 255; i++ )
		{
			state[i] = ((GetKeyState( i ) & 0x800) != 0);
		}
		InputModule::getSingleton().passKeyboardState( state ); 

		// Swap buffers and PREPARE FOR DRAWING!!
		glLoadIdentity();
		SwapBuffers( mhDC );
		glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
		glLoadIdentity();
		SetCursor( LoadCursor( NULL, IDC_ARROW ) );

		return 0;
	}

	void OpenglModule::resizeWindow( int width, int height )
	{
		glViewport(0, 0, width, height);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluOrtho2D( 0, 1024, 768-35, -35 );
		//gluPerspective( 45.0f, (float)width/height, 0.1f, 100.0f );
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
	}

	void OpenglModule::drawRect( Vector2d vPos, Vector2d vDims )
	{
		// Simple function to draw rectangles to the screen.
		glBegin( GL_QUADS );
			glVertex2f( vPos.x, vPos.y );
			glVertex2f( vPos.x+vDims.x, vPos.y );
			glVertex2f( vPos.x+vDims.x, vPos.y+vDims.y );
			glVertex2f( vPos.x, vPos.y+vDims.y );
		glEnd();
	}

	void OpenglModule::clearColor( Vector3d vColor )
	{
		// Set the clear color for opengl
		glClearColor( vColor.x, vColor.y, vColor.z, 1 );
	}

	ModularEngine::TextureID *OpenglModule::loadTexture( char *pTexturePath, char *pMacro )
	{

		// Iterate through the texture list. 
		// If the texturename given matches either an existing path or macro name
		// return the image id.
		// If, for some reason, the macroname given matches an existing macro name then throw error.
		// Reason for this is that if it hits this if statement, then the paths don't match.
		// Can't have 1 macro point to 2 paths.
		for( std::list<TextureID>::iterator i = mlTextures.begin(); i != mlTextures.end(); i++ )
		{
			if( !strcmp( pTexturePath, i->fileName() ) )
			{
				return &(*i);
			}

			// Previously I forgot to check if macroName existed because if it doesn't exist
			// it's set automatically to NULL
			if( i->macroName() )
			{
				if( !strcmp( pTexturePath, i->macroName() ) )
					return &(*i);
			}

			if( pMacro && i->macroName() )
			{
				if( !strcmp( pMacro, i->macroName() ) )
				{
					THROWERROR( "Macro Name Already Exists." );
					return new TextureID();
				}
			}
		}

		// Load the texture using the ImageLoader class
		ModularEngine::Texture textureData = ModularEngine::ImageLoader::getSingleton().LoadTexture( pTexturePath );

		// Image loader returns a class w/ zeros if there's an error.  Check for this.
		if( textureData.mData == 0 )
		{
			//THROWERROR( "Error loading texture" );
			return new TextureID();
		}

		// Determines whether or not there's an alpha channel.
		unsigned int type = GL_RGBA;
		if( textureData.mBpp == 3 )
			type=GL_RGB;

		// Set File Name
		TextureID *nTexture = new TextureID();
		nTexture->fileName( pTexturePath );
		nTexture->mWidth = textureData.mWidth;
		nTexture->mHeight = textureData.mHeight;

		// If there is no macro name, no use setting it.
		if( pMacro )
			nTexture->macroName( pMacro );		

		
		// Enable textures
		glEnable( GL_TEXTURE_2D );
	
		// Create an unused texture id for opengl
		glGenTextures( 1, &nTexture->mTexNum );
		
		// Bind newly created texture to GL_TEXTURE_2D
		glBindTexture( GL_TEXTURE_2D, nTexture->mTexNum );
		
		// Set appropriate txture filters
		// BIG TODO: FIX ME
		// Crashes when MIN_FILTER uncommented.
		glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		
		glTexImage2D( GL_TEXTURE_2D, 0, type, textureData.mWidth, textureData.mHeight, 0, type, GL_UNSIGNED_BYTE, textureData.mData );

		mlTextures.push_back( *nTexture );

		delete [] textureData.mData;
		textureData.mData = 0;
		
		return nTexture;		
	}

	void OpenglModule::drawImage(char *pImage, ModularEngine::Vector2d vPos )
	{
		// load the image and enable textures
		TextureID *image = loadTexture( pImage );
		glEnable( GL_TEXTURE_2D );

		glBindTexture( GL_TEXTURE_2D, image->mTexNum );
	
		// only freezes when this section is carried out.
		glBegin( GL_QUADS );
			glTexCoord2f( 0, 1 );
			glVertex2f( vPos.x, vPos.y );
			glTexCoord2f( 1, 1 );
			glVertex2f( vPos.x+image->mWidth, vPos.y );
			glTexCoord2f( 1, 0 );
			glVertex2f( vPos.x+image->mWidth, vPos.y+image->mHeight );
			glTexCoord2f( 0, 0 );
			glVertex2f( vPos.x, vPos.y+image->mHeight );
		glEnd();

		glDisable( GL_TEXTURE_2D );
	}

	void OpenglModule::drawImage(char *pImage, ModularEngine::Vector2d vPos, ModularEngine::Vector2d vDims)
	{
		TextureID *image = loadTexture( pImage );
		
		glEnable( GL_TEXTURE_2D );
		glBindTexture( GL_TEXTURE_2D, image->mTexNum );
		glBegin( GL_QUADS );
			glTexCoord2f( 0, 1 );
			glVertex2f( vPos.x, vPos.y );
			glTexCoord2f( 1, 1 );
			glVertex2f( vPos.x+vDims.x, vPos.y );
			glTexCoord2f( 1, 0 );
			glVertex2f( vPos.x+vDims.x, vPos.y+vDims.y );
			glTexCoord2f( 0, 0 );
			glVertex2f( vPos.x, vPos.y+vDims.y );
		glEnd();

		glDisable( GL_TEXTURE_2D );
		

	}

	void OpenglModule::drawGradientRect(ModularEngine::Vector2d vPos, ModularEngine::Vector2d vDims, ModularEngine::Vector3d vStartColor, ModularEngine::Vector3d vEndColor, bool horizontal)
	{
		if( !horizontal )
		{
			// Draw a rectangle with a vertical gradient.
			glBegin( GL_QUADS );
				glColor3f( vStartColor.x, vStartColor.y, vStartColor.z );
				glVertex2f( vPos.x, vPos.y );
				glVertex2f( vPos.x+vDims.x, vPos.y );
				glColor3f( vEndColor.x, vEndColor.y, vEndColor.z );
				glVertex2f( vPos.x+vDims.x, vPos.y+vDims.y );
				glVertex2f( vPos.x, vPos.y+vDims.y );
			glEnd();
		} else
		{
			glBegin( GL_QUADS );
				glColor3f( vStartColor.x, vStartColor.y, vStartColor.z );
				glVertex2f( vPos.x, vPos.y );
				glVertex2f( vPos.x, vPos.y+vDims.y );
				glColor3f( vEndColor.x, vEndColor.y, vEndColor.z );
				glVertex2f( vPos.x+vDims.x, vPos.y+vDims.y );
				glVertex2f( vPos.x+vDims.x, vPos.y );	
			glEnd();

		}

	}

	void OpenglModule::setCoords( Vector2d vVec )
	{
		// Move the coord system out.
		resetCoords();
		glTranslatef( vVec.x, vVec.y, 0);
	}

	void OpenglModule::resetCoords()
	{
		// Load the identity matrix.
		glLoadIdentity();
	}

	bool OpenglModule::pointInRect( Vector2d vPoint, Vector2d vPos, Vector2d vDims )
	{
		// Check to see if a 2d point is in a rectangle.

		if( vPoint.x > vPos.x && vPoint.y > vPos.y )
		{
			if( vPoint.x < vPos.x+vDims.x && vPoint.y < vPos.y+vDims.y )
			{
				return true;
			}
		}
		return false;
	}

	void OpenglModule::drawText( char *text, Vector2d vPos )
	{
		// draw text to the screen

		glRasterPos2f( vPos.x, vPos.y-mLogFont.lfHeight );
		glListBase( mFontList );
		glCallLists( strlen( text ), GL_UNSIGNED_BYTE, text );
	}

	void OpenglModule::setWindowHandle( HWND hWnd )
	{
		mHwnd = hWnd;
	}

	void OpenglModule::setLoadTextureFunctor(ModularEngine::LoadTextureFunctor *func)
	{
		texLoader = func;
	}

	void OpenglModule::drawBezier( Vector3d *points, int nPoints, float interval)
	{
		glLineWidth( 2 );
		glColor3f( 1, 0, 0 );
		glBegin( GL_LINE_STRIP );
		for( float i = 0; i <= 1; i+=interval )
		{
			Vector3d temp = points[0] * pow( (float)(1-i), 2 ) + points[1]*2*i*(1-i) + points[2]*pow( (float)i, 2 );
			glVertex3f( temp.x, temp.y, temp.z );
		}
		glEnd();

	}

	Vector3d OpenglModule::drawBezierProc( Vector3d *points, int nPoints, float t )
	{
		Vector3d result = Vector3d( 0, 0, 0 );

		for( int i = 0; i < nPoints; i++ )
		{
			result += points[i] * pow( (float)1-t, nPoints-i ) * pow( (float)t, i );
		}

		return result;
	}

	void OpenglModule::drawCubicHermite(ModularEngine::Vector3d *points, float c, float interval)
	{
		glBegin( GL_LINE_STRIP );
		for( float i = 0; i <= 1; i+=interval )
		{
			Vector3d temp = drawCubicHermiteProc( points, c, i );
			glVertex3f( temp.x, temp.y, temp.z );
			
		}	
		glVertex3f( points[2].x, points[2].y, points[2].z );
		glEnd();

	}

	Vector3d OpenglModule::drawCubicHermiteProc(ModularEngine::Vector3d *points, float c, float t)
	{
		// The following is a the equation for Cardinal Splines.
		// Setting c to zero makes it a catmull rom spline.
		Vector3d m1 = (points[2]-points[0])*(1-c)/2;
		Vector3d m2 = (points[3]-points[1])*(1-c)/2;

		Vector3d result = points[1] * (2*t*t*t-3*t*t+1);
		result += m1 * (t*t*t-2*t*t+t);
		result += points[2] * (-2*pow((float)t,3)+ 3*pow((float)t,2));
		result += m2 * (t*t*t-t*t);
		return result;
							


	}



}

